using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace Dsp
{
    public enum RawFileFormat
    {
        /// <summary>08-bit Linear Signed   Integer PCM</summary>
        S_PCM08,
        /// <summary>08-bit Linear Unsigned Integer PCM</summary>
        U_PCM08,
        /// <summary>16-bit Linear Signed   Integer PCM</summary>
        S_PCM16,
        /// <summary>16-bit Linear Unsigned Integer PCM</summary>
        U_PCM16,
        /// <summary>24-bit Linear Signed   Integer PCM</summary>
        S_PCM24,
        /// <summary>24-bit Linear Unsigned Integer PCM</summary>
        U_PCM24,
        /// <summary>32-bit Floating Point IEEE 754</summary>
        FLOAT32,
        /// <summary>64-bit Floating Point IEEE 754</summary>
        FLOAT64
    }



    /// <summary>
    /// Reads a *mono* raw file. Supports different sample formats:
    /// * S_PCM08: 08-bit Unsigned Linear PCM
    /// * U_PCM08: 08-bit Signed   Linear PCM
    /// * S_PCM16: 16-bit Signed   Linear PCM
    /// * U_PCM16: 16-bit Unsigned Linear PCM
    /// * S_PCM32: 24-bit Signed   Linear PCM
    /// * U_PCM32: 24-bit Unsigned Linear PCM
    /// * FLOAT32: 32-bit Floating Point (IEEE 754)
    /// * FLOAT64: 64-bit Floating Point (IEEE 754)
    /// 
    /// Note: The opened stream is automaticaly closed when read pointer reaches the EOF, and then
    /// the property-flag Closed is asserted.   
    /// 
    /// Warning: Dinamic range of PCM formats is limited to range [-1, +1)
    /// 
    /// References:
    /// http://www.sonicspot.com/guide/wavefiles.html
    /// </summary>
    public class RawFileReader 
    {
        private RawFileFormat _format;
        
        private string        _filename;           // if aplicable, if not null
        private Stream        _stream;             // generic stream with CanSeek := true
        private BinaryReader  _reader;
        private int           _sampleXferd;        // number of samples tranfered
        private bool          _closed;             // EOF and closed stream?

        #region Constructors
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="filename">File name created wiwth FileMode.Create (OVERWRITE MODE!)</param>
        /// <param name="format">Sample format</param>
        /// <param name="fs">Sampling frequency</param>
        public RawFileReader(string filename, RawFileFormat fmt)
        {
            _filename = filename;
            _stream = new FileStream(_filename, FileMode.Open);

            Constructor(_stream, fmt);
        }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="stream">Any Stream Class derived object with the property CanSeek set to true
        /// like files and memory streams but it isn't valid sockets, serial ports or the like streams
        /// </param>
        /// <param name="format">Sample format</param>
        /// <param name="fs">Sampling frequency</param>
        public RawFileReader(Stream stream, RawFileFormat fmt)
        {
            Constructor(stream, fmt);
        }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="type">Access type; for writting or reading</param>
        /// <param name="filename">File name to CREATE for write</param>
        private void Constructor(Stream stream, RawFileFormat fmt)
        {           
            _stream      = stream;
            _format      = fmt;
            _reader      = new BinaryReader(_stream);
            _sampleXferd = 0;
            _closed      = false;
        }
        #endregion

     
        /// <summary>
        /// Reads audio samples from the WAV file
        /// </summary>
        /// <param name="buffer">Array with data</param>
        /// <param name="offset">Offset in the array</param>
        /// <param name="count">Number of samples that will be read</param>
        public int XferData(double[] buffer, int offset, int count)
        {  
            // samples in different formats
            double s;

            byte   u08;
            sbyte  s08;
            ushort u16;
            short  s16;
            int    s24;            
            byte   s24_1;
            byte   s24_2;
            byte   s24_3;
            float  f32;
            double f64;

            int items = 0;

            for (int i = offset; i < offset + count; i++)
            {
                // if EOF
                if (_stream.Position == _stream.Length)
                    break;
                
                switch (_format)
                {
                    case RawFileFormat.U_PCM08:
                        u08 = _reader.ReadByte(); 
                        s   = (((double) u08) / 00255.0) * 2 - 1; 
                        break;

                    case RawFileFormat.S_PCM08:
                        s08 = _reader.ReadSByte();
                        s   = (double) s08 / 00128.0;
                        break;

                    case RawFileFormat.U_PCM16:
                        u16 = _reader.ReadUInt16();
                        s   = (((double) u16) / 65535.0) * 2 - 1;
                        break;

                    case RawFileFormat.S_PCM16: 
                        s16 = _reader.ReadInt16(); 
                        s   = (double) s16 / 32768.0; 
                        break;

                    case RawFileFormat.S_PCM24:
                        s24_1 = _reader.ReadByte();
                        s24_2 = _reader.ReadByte();
                        s24_3 = _reader.ReadByte();
                        
                        // keep the sign bit, stupid!                        
                        if ((s24_3 & 0x80) != 0)
                            unchecked { s24 = (int) 0xFF000000; }
                        else
                            unchecked { s24 = (int) 0x00000000; }
                        
                        s24 |= (s24_3 << 16) | (s24_2 << 08) | s24_1;
                        s   = (double) s24 / 8388607.0; 
                        break;

                    case RawFileFormat.U_PCM24:
                        s = 0;
                        break;

                    case RawFileFormat.FLOAT32: 
                        f32 = _reader.ReadSingle();
                        s   = (float ) f32;
                        break;
                    case RawFileFormat.FLOAT64:
                        f64 = _reader.ReadDouble();
                        s   = (double) f64;
                        break;
                    
                    default:
                        s = 0;
                        break;
                }


                buffer[i] = s;
                _sampleXferd++;
                items++;
            }

            return items;
        }
        
        ~RawFileReader()
        {
            if (!_closed)
                Close();
        }

        #region Properties
        public bool          Closed       { get { return _closed; } }
        public Stream        Stream       { get { return _stream; } }
        public RawFileFormat SampleFormat { get { return _format; } }
        #endregion

        /// <summary>
        /// Close the open stream (file)
        /// </summary>
        public void Close()
        {
            _reader.Close();
            _stream.Close();
            _closed = true;
        }
    }
}